package bsh.dyn;

import java.lang.reflect.Array;

/**
 * Tipo intermediario para fazer casts em runtime.
 * 
 * @author takeshi
 */
public final class DynamicObject {

    private final Object target;

    /**
     * @param target Object
     */
    private DynamicObject(Object target) {
        super();
        this.target = target;
    }

    /**
     * @return Number
     */
    public Number toNumber() {
        return ((Number) this.target);
    }

    /**
     * @return int
     */
    public int toInt() {
        return this.toNumber().intValue();
    }

    /**
     * @return primitiva
     */
    public boolean toBoolean() {
        return this.target != null ? ((Boolean) this.target).booleanValue() : false;
    }

    /**
     * @return primitiva
     */
    public short toShort() {
        return this.toNumber().shortValue();
    }

    /**
     * @return primitiva
     */
    public char toChar() {
        return ((Character) this.target).charValue();
    }

    /**
     * @return primitiva
     */
    public byte toByte() {
        return this.toNumber().byteValue();
    }

    /**
     * @return primitiva
     */
    public long toLong() {
        return this.toNumber().longValue();
    }

    /**
     * @return primitiva
     */
    public float toFloat() {
        return this.toNumber().floatValue();
    }

    /**
     * @return primitiva
     */
    public double toDouble() {
        return this.toNumber().doubleValue();
    }

    /**
     * Faz um cast, com o caso especial de, se o tipo alvo for uma string, chama {@link String#valueOf(Object)}
     * 
     * @param type Class
     * @return Object
     */
    public Object castTo(Class<?> type) {
        if (this.target == null) {
            return null;
        }
        if (type.isInstance(this.target)) {
            return this.target;
        }
        if (String.class.equals(type)) {
            return String.valueOf(this.target);
        }
        if (type.isArray() && this.target.getClass().isArray()) { // may be compatible
            final int length = Array.getLength(this.target);
            Class<?> targetType = type.getComponentType();
            if (length == 0) { // special case empty arrays
                return Array.newInstance(targetType, 0);
            }
            Object targetArray = Array.newInstance(targetType, length);
            for (int i = 0; i < length; i++) {
                final Object theValue = Array.get(this.target, i);
                Array.set(targetArray, i, DynamicObject.wrap(theValue).castTo(targetType));
            }
            return targetArray;
        }

        throw new ClassCastException("object of type: " + this.target.getClass() + " cannot be cast to: " + type);
    }

    /**
     * Faz o wrap
     * 
     * @param target Object
     * @return {@link DynamicObject}
     */
    public static DynamicObject wrap(Object target) {
        if (target instanceof DynamicObject) {
            return (DynamicObject) target;
        }
        return new DynamicObject(target);
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#hashCode()
     */

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((this.target == null) ? 0 : this.target.hashCode());
        return result;
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (this.getClass() != obj.getClass()) {
            return false;
        }
        DynamicObject other = (DynamicObject) obj;
        if (this.target == null) {
            if (other.target != null) {
                return false;
            }
        } else if (!this.target.equals(other.target)) {
            return false;
        }
        return true;
    }

}
